/**
 * Copyright (C) 2010-2018 Gordon Fraser, Andrea Arcuri and EvoSuite
 * contributors
 * <p>
 * This file is part of EvoSuite.
 * <p>
 * EvoSuite is free software: you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published
 * by the Free Software Foundation, either version 3.0 of the License, or
 * (at your option) any later version.
 * <p>
 * EvoSuite is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser Public License for more details.
 * <p>
 * You should have received a copy of the GNU Lesser General Public
 * License along with EvoSuite. If not, see <http://www.gnu.org/licenses/>.
 */
package org.evosuite.utils.generic;

import com.googlecode.gentyref.GenericTypeReflector;
import org.evosuite.Properties;
import org.evosuite.ga.ConstructionFailedException;

import java.io.Serializable;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.Collection;
import java.util.List;
import java.util.Map;

/**
 * Interface for a generic class representation.
 * <p>
 * All instantiate of this interface should be generated by {@code GenericClassFactory}
 * in order to ensure only one implementation is used at runtime.
 */
public interface GenericClass<T extends GenericClass<T>> extends Serializable {

    T self();

    /**
     * Determine if there exists an instantiation of the type variables such that the class matches
     * otherType
     *
     * @param otherType is the class we want to generate
     * @return whether this generic class can be instantiated to the given type.
     */
    boolean canBeInstantiatedTo(GenericClass<?> otherType);

    /**
     * change the class loader of this generic class.
     *
     * @param loader the class loader to be used.
     * @return whether changing the class loader was successful,.
     */
    boolean changeClassLoader(ClassLoader loader);

    /**
     * Get the boxed type of the represented generic class, if it is a primitive type.
     * If not, the rawClass is returned.
     *
     * @return Boxed type for primitive types, otherwise the raw class.
     */
    Class<?> getBoxedType();

    /**
     * The name of the represented generic class.
     * See {@link Class#getName()} for the exact format of the name.
     *
     * @return the name of the raw class.
     */
    String getClassName();

    /**
     * If the represented generic class represents an array, return the component class of the array.
     * <p>
     * E.g:
     * GenericClass(int[].class) has the component type int
     * GenericClass(A<B>[].class) has the component type A<B>
     * <p>
     * If the generic class is not an array, its behaviour is undefined.
     *
     * @return the component type of the array
     */
    GenericClass<?> getComponentClass();

    /**
     * If the represented generic class represents an aray, return the name of its component class.
     * <p>
     * If the generic class is not an array, its behaviour is undefined.
     *
     * @return the name of the component type.
     */
    String getComponentName();

    /**
     * Alias for {@code TypeUtils#getArrayComponentType} with getType() as argument.
     *
     * @return the component type of the array as provided from TypeUtils.
     */
    Type getComponentType();

    /**
     * Compute the generic boundaries of this generic class.
     *
     * @return the generic boundaries.
     */
    Collection<GenericClass<?>> getGenericBounds();

    /**
     * Generate a possible instantiation of this generic class randomly.
     * All type variables are instantiated randomly.
     * <p>
     * The generic depth is assumed to be 0, e.g. this generic class is not used as parameter of another generic class.
     * <p>
     * If this generic class has no wildcards or type variables, a copy of this generic class is returned.
     *
     * @return an instantiation of this generic class.
     * @throws ConstructionFailedException if no instantiation could be constructed.
     */
    GenericClass<?> getGenericInstantiation() throws ConstructionFailedException;

    /**
     * Generate a possible instantiation of this generic class randomly.
     * <p>
     * The generic depth is assumed to be 0, e.g. this generic class is not used as parameter of another generic class.
     * <p>
     * If this generic class has no wildcards or type variables, a copy of this generic class is returned.
     *
     * @param typeMap Constraints to the type variables of this generic class.
     * @return an instantiation of this generic class.
     * @throws ConstructionFailedException if no instantiation could be constructed.
     */
    GenericClass<?> getGenericInstantiation(Map<TypeVariable<?>, Type> typeMap) throws ConstructionFailedException;

    /**
     * Generate a possible instantiation of this generic class randomly.
     * <p>
     * The current generic depth of the instantiation can be set with {@param recursionLevel} to ensure the
     * maximal generic depth {@link Properties#MAX_GENERIC_DEPTH} is respected.
     * This should only be relevant for instantiating the parameter of another generic class.
     * <p>
     * If this generic class has no wildcards or type variables, a copy of this generic class is returned.
     *
     * @param typeMap        Constraints to the type variables of this generic class.
     * @param recursionLevel the maximal generic depth of the instantiation.
     * @return an instantiation of this generic class.
     * @throws ConstructionFailedException if no instantiation could be constructed.
     */
    GenericClass<?> getGenericInstantiation(Map<TypeVariable<?>, Type> typeMap, int recursionLevel) throws ConstructionFailedException;

    /**
     * Get the interfaces, that this class implements.
     *
     * @return a List of {@link GenericClass}, each element represents one interface.
     */
    List<GenericClass<?>> getInterfaces();

    /**
     * Get the number of type parameters of this type.
     * This does not include type parameters of super classes that are fixed by this class.
     *
     * @return the number of type parameters of this class.
     */
    int getNumParameters();

    /**
     * If this is a parameterized type, its owner type is returned as a GenericClass.
     * <p>
     * IF this is not a parameterized type, its behaviour is undefined.
     *
     * @return the owner type as {@link GenericClass}.
     */
    GenericClass<?> getOwnerType();

    /**
     * A list containing the generic type parameters (as {@link Type} objects) of the represented generic class.
     * <p>
     * The order of the list is equal to the order of the generic parameters (from left to right).
     *
     * @return a view of the described list.
     */
    List<Type> getParameterTypes();

    /**
     * A list containing the generic type parameters (as {@link GenericClass} objects) of the represented generic class.
     * <p>
     * The order of the list is equal to the order of the generic parameters (from left to right).
     *
     * @return a view of the described list.
     */
    List<GenericClass<?>> getParameterClasses();

    /**
     * Get the raw class (without generic information) of this class.
     *
     * @return the raw class.
     */
    Class<?> getRawClass();

    /**
     * Get the raw component class of this instances raw class.
     *
     * @return the raw component class, if it exists, else null
     */
    Type getRawComponentClass();

    /**
     * Get the simple name of the type represented by this instance.
     *
     * @return the simple name.
     */
    String getSimpleName();

    /**
     * Get the generic super class of this generic class.
     * <p>
     * The generic information of the super class is inferred from this generic class.
     * <p>
     * See {@link GenericTypeReflector#getExactSuperType} for additional information.
     *
     * @return the generic super class.
     */
    GenericClass<?> getSuperClass();

    /**
     * Get the represented {@link java.lang.reflect.Type} object.
     */
    Type getType();

    /**
     * Alias for {@link GenericTypeReflector#getTypeName} of the represented class.
     */
    String getTypeName();

    /**
     * The type variable map is a mapping from a {@link TypeVariable} to a {@link Type} object:
     * <p>
     * The key set of the mapping is the set of type variables of this generic class.
     * THe value of every set is the constraint, of this type variable for this generic class.
     *
     * @return an accessor of the type variable map
     */
    Map<TypeVariable<?>, Type> getTypeVariableMap();

    /**
     * Return a list of type variables of this type, or an empty list if this is not a parameterized
     * type
     *
     * @return a view of the list.
     */
    List<TypeVariable<?>> getTypeVariables();

    /**
     * Return the unboxed type of this generic class if this is a wrapper type (e.g. {@link Integer})
     *
     * @return the raw class of the unboxed type.
     */
    Class<?> getUnboxedType();

    /**
     * Get an instance of the generic class with a new component type.
     *
     * @param componentClass the new component type
     * @return The newly generated instance if this is an array type, else a copy of this type.
     */
    GenericClass<?> getWithComponentClass(GenericClass<?> componentClass);

    GenericClass<?> getWithGenericParameterTypes(List<T> parameters);

    /**
     * Creates a new instance, with "inherited" type variables from a superclass.
     *
     * @param superClass the superclass.
     * @return the new instance.
     * @throws ConstructionFailedException if the new instance can't be constructed.
     */
    GenericClass<?> getWithParametersFromSuperclass(GenericClass<?> superClass) throws ConstructionFailedException;

    /**
     * Creates a new instance of this generic class with set parameter types.
     *
     * @param parameters the new parameter types.
     * @return the new instance.
     */
    GenericClass<?> getWithParameterTypes(List<Type> parameters);

    /**
     * Creates a new instance of this generic class with set parameter types.
     *
     * @param parameters the new parameter types.
     * @return the new instance.
     */
    GenericClass<?> getWithParameterTypes(Type[] parameters);

    /**
     * Replaces all type parameters with unbound wildcards ("?").
     * <p>
     * An example implementation on type level is: {@link GenericTypeReflector#addWildcardParameters(Class)}
     * An implementation of this function may only be a wrapper of this function.
     *
     * @return This generic class with replaced parameters.
     */
    GenericClass<?> getWithWildcardTypes();

    /**
     * Checks if another generic type is a super type of this type.
     *
     * @param superType the type to be checked.
     * @return Whether {@param superType} is a supertype of this
     */
    boolean hasGenericSuperType(GenericClass<?> superType);

    boolean hasGenericSuperType(Type superType);

    /**
     * Whether the generic class has an owner type, e.g. is an inner class.
     *
     * @return Whether an owner type is present.
     */
    boolean hasOwnerType();

    /**
     * Check if the represented generic class contains any type parameters
     * or wildcards.
     * <p>
     * For inner classes, the type parameters and wildcards of the owner type
     * are also considered.
     * <p>
     * If the represented generic class is a array type,
     * the method call is delegated to the component class of the array.
     *
     * @return Whether the generic class has any type parameter or wildcard
     */
    boolean hasWildcardOrTypeVariables();

    /**
     * Checks if this generic class contains or is a type variables.
     * <p>
     * If this generic class is a parameterized type,
     * all parameter types are checked recursively for type variables.
     * <p>
     * E.g. A<B<T>> returns true, if T is a type variable
     *
     * @return Whether type has a type variable.
     */
    boolean hasTypeVariables();


    /**
     * Checks if this generic class contains or is a wildcard types.
     * <p>
     * If this generic class is a parameterized type,
     * all parameter types are checked recursively for wildcards.
     * <p>
     * E.g. A<B<?>> returns true.
     *
     * @return Whether this generic class contains or is a wildcard type.
     */
    boolean hasWildcardTypes();

    /**
     * @return Whether this generic class is abstract.
     */
    boolean isAbstract();

    /**
     * @return Whether this generic class is anonymous.
     */
    boolean isAnonymous();

    /**
     * @return whether this generic class is an array.
     */
    boolean isArray();

    /**
     * Check if this instance is assignable from {@param rhsType}.
     *
     * @param rhsType the right-hand side of the check.
     * @return Whether this instance is assignable to the other.
     */
    boolean isAssignableFrom(GenericClass<?> rhsType);

    /**
     * Check if this instance is assignable from {@param rhsType}.
     *
     * @param rhsType the right-hand side of the check.
     * @return Whether this instance is assignable to the other.
     */
    boolean isAssignableFrom(Type rhsType);

    /**
     * Check if this instance is assignable to {@param lhsType}.
     *
     * @param lhsType the left-hand side of the check.
     * @return Whether the other instance is assignable to this.
     */
    boolean isAssignableTo(GenericClass<?> lhsType);

    /**
     * Check if this instance is assignable to {@param lhsType}.
     *
     * @param lhsType the left-hand side of the check.
     * @return Whether the other instance is assignable to this.
     */
    boolean isAssignableTo(Type lhsType);

    /**
     * Checks if the raw class of this generic class equals {@link Class}
     *
     * @return Whether the raw class is equal to {@link Class}
     */
    boolean isClass();

    /**
     * Checks if the raw class of this generic class equals {@link Enum}
     *
     * @return Whether the raw class is equal to {@link Enum}
     */
    boolean isEnum();

    /**
     * Checks if the type of this generic class is of type {@link java.lang.reflect.GenericArrayType}
     *
     * @return Whether the type is equal is of type {@link java.lang.reflect.GenericArrayType}
     */
    boolean isGenericArray();

    /**
     * Checks if this instance is a generic super type of {@param subtype}
     *
     * @param subType The subtype to be checked
     * @return Whether this is a supertype of {@param subtype}
     */
    boolean isGenericSuperTypeOf(GenericClass<?> subType);

    boolean isGenericSuperTypeOf(Type subType);

    /**
     * Checks if the raw class of this generic class equals {@link Object}
     *
     * @return Whether the raw class is equal to {@link Object}
     */
    boolean isObject();

    /**
     * Checks if the type of this generic class is of type {@link java.lang.reflect.ParameterizedType}
     *
     * @return Whether the type is equal is of type {@link java.lang.reflect.ParameterizedType}
     */
    boolean isParameterizedType();

    /**
     * Delegation to {@link Class#isPrimitive()} of the raw class.
     *
     * @return Whatever {@link Class#isPrimitive()} returns.
     */
    boolean isPrimitive();

    /**
     * Checks if the type of this generic class is of type {@link Class}
     *
     * @return Whether the type is equal is of type {@link Class}
     */
    boolean isRawClass();

    /**
     * Checks if the type of this generic class is of type {@link java.lang.reflect.TypeVariable}
     *
     * @return Whether the type is equal is of type {@link java.lang.reflect.TypeVariable}
     */
    boolean isTypeVariable();

    /**
     * Checks if the type of this generic class is of type {@link java.lang.reflect.WildcardType}
     *
     * @return Whether the type is equal is of type {@link java.lang.reflect.WildcardType}
     */
    boolean isWildcardType();

    /**
     * Checks if the raw class of this generic class equals {@link String}
     *
     * @return Whether the raw class is equal to {@link String}
     */
    boolean isString();

    /**
     * Checks if the raw class of this generic class equals {@link Object}
     *
     * @return Whether the raw class is equal to {@link Object}
     */
    boolean isVoid();

    /**
     * Checks if the raw class of this generic class equals a wrapper type, e.g. {@link Integer}
     *
     * @return Whether the raw class is equals a wrapper type.
     */
    boolean isWrapperType();

    /**
     * Determine whether the boundaries of the type variable are satisfied
     * without any additional constraints regarding the type variables.
     *
     * @param typeVariable the type variable to be checked
     * @return whether this class satisfies the boundaries of the type variable.
     */
    boolean satisfiesBoundaries(TypeVariable<?> typeVariable);

    /**
     * Determine whether the boundaries of the type variable are satisfied by this class
     * with additional constraints regarding the type variables.
     * <p>
     * Every entry of {@param typeNap} is an constraint that enforces the key/type variable
     * to be an instance of the value/type.
     *
     * @param typeVariable The type variable, that should be checked.
     * @param typeMap      The map of constraints.
     * @return Wether the boundaries of the type variable are satisfied by this class for the given set of constraints.
     */
    boolean satisfiesBoundaries(TypeVariable<?> typeVariable, Map<TypeVariable<?>, Type> typeMap);

    /**
     * Whether this instance satisfies the boundaries of a given wildcard type.
     *
     * @param wildcardType the given wildcard type
     * @return Whether the boundaries are satisfied.
     */
    boolean satisfiesBoundaries(WildcardType wildcardType);

    /**
     * Whether the instance satisfies the boundaries of a given wildcard type.
     *
     * @param wildcardType the given wildcard type
     * @param typeMap      an additional mapping of type variable to type.
     * @return Whether the boundaries are satisfied.
     */
    boolean satisfiesBoundaries(WildcardType wildcardType, Map<TypeVariable<?>, Type> typeMap);

    /**
     * Create a new instance of the generic class from the raw class, e.g. discard changes in the parameter types.
     *
     * @return The new instance.
     */
    GenericClass<?> getRawGenericClass();

    /**
     * Instantiate this wildcard type instance.
     *
     * @param typeMap        a mapping from type variable to type map that shall be used.
     * @param recursionLevel how deep the generic types shall be instantiated.
     * @return Whether the boundaries are satisfied.
     * @throws ConstructionFailedException If this instance can't be instantiated.
     */
    GenericClass<?> getGenericWildcardInstantiation(Map<TypeVariable<?>, Type> typeMap, int recursionLevel) throws ConstructionFailedException;

    /**
     * Sets the represented type of a copy of this generic class
     *
     * @param type the type to be set.
     * @return the copy.
     */
    GenericClass<?> setType(Type type);
}
